package biz

import (
	"bytes"
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/go-kratos/kratos/v2/transport/http"
	v1 "gitlab.cqcb.com/shangyou_mic/dscm-approve-api-pb/api/roses/v1"
	"io"
	"rosesbff/pkg/util"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	libredis "github.com/go-redis/redis"
	stdhttp "net/http"
	"rosesbff/internal/data/redis"
)

const (
	B00 = "B00"
	B01 = "B01"
	B02 = "B02"
	B03 = "B03"
	B04 = "B04"
	B05 = "B05"
	B06 = "B06"
	B07 = "B07"
	B08 = "B08"
	B09 = "B09"
	B0A = "B0A"
	B0B = "B0B"
	B0C = "B0C"
	B0D = "B0D"
	B0E = "B0E"
	B0F = "B0F"
	B10 = "B10"
	B11 = "B11"
	B12 = "B12"
	C00 = "C00"
	C01 = "C01"
	D00 = "D00"
	D01 = "D01"
	D02 = "D02"
)

// systemCfgKeyDesc 系统配置描述
var systemCfgKeyDesc = map[string]string{
	// 温馨提示: {$N}:为配置值, {XX}:为与当前相关的其它配置
	B00: "任务大厅展示发布时间在{$N}小时内的稿件",
	B01: "审读任务必须在{$N}小时内完成, 超时扣除{XX}积分",
	B02: "审读任务必须在{XX}小时内完成, 超时扣除{$N}积分",
	B03: "本月内误判超过{$N}次时, 自动封禁账号{XX}天",
	B04: "本月内误判超过{XX}次时, 自动封禁账号{$N}天",
	B05: "稿件被{$N}个人判定为无误时自动结案",
	B06: "稿件被{$N}个人判定有错时, 需另外{XX}个人再次判定",
	B07: "稿件被{XX}个人判定有错时, 需另外{$N}个人再次判定",
	B08: "每月提现限额{$N}积分, 每人每月提现限额{XX}积分, 最小限额{XX}积分, 每月每人限提{XX}次",
	B09: "每月提现限额{XX}积分, 每人每月提现限额{$N}积分, 最小限额{XX}积分, 每月每人限提{XX}次",
	B0A: "每月提现限额{XX}积分, 每人每月提现限额{XX}积分, 最小限额{$N}积分, 每月每人限提{XX}次",
	B0B: "每月提现限额{XX}积分, 每人每月提现限额{XX}积分, 最小限额{XX}积分, 每月每人限提{$N}次",
	B0C: "月度总得分不能超过{$N}分",
	B0D: "单条错误分值范围(机审){$N}-{XX}",
	B0E: "单条错误分值范围(机审){XX}-{$N}",
	B0F: "单条错误分值范围(人审){$N}-{XX}",
	B10: "单条错误分值范围(人审){XX}-{$N}",
	B11: "复核员在{$N}分钟内未执行复核操作,自动取消复核员与稿件的绑定",
	B12: "一元对换{$N}积分",
	C00: "审读量系数{$N}",
	C01: "差错量系数{$N}",
	D00: "差错位置{$N}",
	D01: "差错类型{$N}",
	D02: "常用备注{$N}",
}

// systemCfgKeyValDefault 系统配置默认值
var systemCfgKeyValDefault = map[string]string{
	B00: "12",                                      // int
	B01: "1",                                       // int
	B02: "5",                                       // int
	B03: "5",                                       // int
	B04: "7",                                       // int
	B05: "3",                                       // int
	B06: "1",                                       // int
	B07: "1",                                       // int
	B08: "50000",                                   // int
	B09: "1000",                                    // int
	B0A: "100",                                     // int
	B0B: "1",                                       // int
	B0C: "10000",                                   // int
	B0D: "0",                                       // int
	B0E: "1",                                       // int
	B0F: "0",                                       // int
	B10: "5",                                       // int
	B11: "60",                                      // int
	B12: "10",                                      // int
	C00: "1.0",                                     // float64
	C01: "1.0",                                     // float64
	D00: "标题,小标,文内,图片,图说,视频,链接,编排", // []string
	D01: "政治性差错,事实性差错,技术性差错",        // []string
	D02: "总书记专题,重要表述,重要问题",            // []string
}

type SystemConfig struct {
	sync.RWMutex
	common   *Common
	defaults *SystemConfig // 默认配置

	Axx map[string]string  // 字符串配置值
	Bxx map[string]int64   // 整型配置值
	Cxx map[string]float64 // 浮点型配置值
	Dxx map[string]string  // 字符串数组配置值
}

func (s *SystemConfig) WithLock(fc func()) {
	s.Lock()
	defer s.Unlock()
	fc()
}

func (s *SystemConfig) WithRLock(fc func()) {
	s.RLock()
	defer s.RUnlock()
	fc()
}

func (s *SystemConfig) Len() int {
	return len(s.Axx) + len(s.Bxx) + len(s.Cxx)
}

// Load 加载配置数据到内存变量中
func (s *SystemConfig) Load(cfg map[string]string) (err error) {
	s.WithLock(func() {
		for key, val := range cfg {
			if key == "" {
				continue
			}
			switch key[0] {
			case 'A':
				s.Axx[key] = val
			case 'B':
				i := int64(0)
				val = strings.ReplaceAll(val, " ", "")
				i, err = strconv.ParseInt(val, 10, 64)
				if err != nil {
					return
				}
				s.Bxx[key] = i
			case 'C':
				f := float64(0)
				val = strings.ReplaceAll(val, " ", "")
				f, err = strconv.ParseFloat(val, 64)
				if err != nil {
					return
				}
				s.Cxx[key] = f
			case 'D':
				s.Dxx[key] = val
			default:
			}
		}
	})
	return
}

// Save 持久化配置数据到持久化媒介
func (s *SystemConfig) Save() error {
	save := &v1.SaveSystemConfigRequest{
		Lists: make([]*v1.SystemConfig, 0, 1<<3),
	}
	s.WithRLock(func() {
		for k, v := range s.Axx {
			save.Lists = append(save.Lists, &v1.SystemConfig{
				CfgKey: k,
				CfgVal: &v,
				Note:   systemCfgKeyDesc[k],
			})
		}
		for k, v := range s.Bxx {
			value := fmt.Sprintf("%d", v)
			save.Lists = append(save.Lists, &v1.SystemConfig{
				CfgKey: k,
				CfgVal: &value,
				Note:   systemCfgKeyDesc[k],
			})
		}
		for k, v := range s.Cxx {
			value := strconv.FormatFloat(v, 'f', -1, 64)
			save.Lists = append(save.Lists, &v1.SystemConfig{
				CfgKey: k,
				CfgVal: &value,
				Note:   systemCfgKeyDesc[k],
			})
		}
		for k, v := range s.Dxx {
			save.Lists = append(save.Lists, &v1.SystemConfig{
				CfgKey: k,
				CfgVal: &v,
				Note:   systemCfgKeyDesc[k],
			})
		}
	})
	_, err := s.common.SaveSystemConfig(context.Background(), save)
	if err != nil {
		return err
	}
	return nil
}

// VerifyLoad 校验加载的配置数据是否完整
func (s *SystemConfig) VerifyLoad() (err error) {
	s.WithRLock(func() {
		for key := range s.defaults.Axx {
			if _, ok := s.Axx[key]; !ok {
				err = fmt.Errorf("缺少配置信息: %s", systemCfgKeyDesc[key])
				return
			}
		}
		for key := range s.defaults.Bxx {
			if _, ok := s.Bxx[key]; !ok {
				err = fmt.Errorf("缺少配置信息: %s", systemCfgKeyDesc[key])
				return
			}
		}
		for key := range s.defaults.Cxx {
			if _, ok := s.Cxx[key]; !ok {
				err = fmt.Errorf("缺少配置信息: %s", systemCfgKeyDesc[key])
				return
			}
		}
		for key := range s.defaults.Dxx {
			if _, ok := s.Dxx[key]; !ok {
				err = fmt.Errorf("缺少配置信息: %s", systemCfgKeyDesc[key])
				return
			}
		}
	})
	return
}

func (s *SystemConfig) Set(key string, val any) *SystemConfig {
	key = strings.ReplaceAll(key, " ", "")
	if key == "" {
		return s
	}
	switch key[0] {
	case 'A':
		if v, ok := val.(string); ok {
			s.WithLock(func() { s.Axx[key] = v })
		}
	case 'B':
		if v, ok := val.(int); ok {
			s.WithLock(func() { s.Bxx[key] = int64(v) })
		}
		if v, ok := val.(int64); ok {
			s.WithLock(func() { s.Bxx[key] = v })
		}
	case 'C':
		if v, ok := val.(float64); ok {
			s.WithLock(func() { s.Cxx[key] = v })
		}
	case 'D':
		if v, ok := val.([]string); ok {
			value := strings.Join(v, ",")
			s.WithLock(func() { s.Dxx[key] = value })
		}
		if v, ok := val.(string); ok {
			s.WithLock(func() { s.Dxx[key] = v })
		}
	default:
	}
	return s
}

// String 获取字符串类型的配置值
func (s *SystemConfig) String(key string) (v string) {
	s.WithRLock(func() { v = s.Axx[key] })
	return
}

// Int 获取整数类型的配置值
func (s *SystemConfig) Int(key string) (v int64) {
	s.WithRLock(func() { v = s.Bxx[key] })
	return
}

// Float 获取浮点数类型的配置值
func (s *SystemConfig) Float(key string) (v float64) {
	s.WithRLock(func() { v = s.Cxx[key] })
	return
}

// Strings 获取字符串数组类型的配置值
func (s *SystemConfig) Strings(key string) []string {
	val := ""
	ok := false
	s.WithRLock(func() { val, ok = s.Dxx[key] })
	if !ok {
		return make([]string, 0)
	}
	val = strings.TrimSpace(val)
	return strings.Split(val, ",")
}

func newSystemConfig(common *Common) *SystemConfig {
	result := &SystemConfig{
		common: common,
		Axx:    make(map[string]string),
		Bxx:    make(map[string]int64),
		Cxx:    make(map[string]float64),
		Dxx:    make(map[string]string),
	}
	result.defaults = &SystemConfig{
		common: common,
		Axx:    make(map[string]string),
		Bxx:    make(map[string]int64),
		Cxx:    make(map[string]float64),
		Dxx:    make(map[string]string),
	}
	return result
}

type CommonRepo interface {
	LoadSystemConfig(ctx context.Context, req *v1.LoadSystemConfigRequest) (*v1.LoadSystemConfigReply, error)
	SaveSystemConfig(ctx context.Context, req *v1.SaveSystemConfigRequest) (*v1.SaveSystemConfigReply, error)

	SystemConfigLists(ctx context.Context, req *v1.SystemConfigListsRequest) (*v1.SystemConfigListsReply, error)
	SystemConfigUpdate(ctx context.Context, req *v1.SystemConfigUpdateRequest) (*v1.SystemConfigUpdateReply, error)

	TopicLists(ctx context.Context, req *v1.TopicListsRequest) (*v1.TopicListsReply, error)
	WechatLoginUserinfo(ctx context.Context, req *v1.WechatLoginUserinfoRequest) (*v1.WechatLoginUserinfoReply, error)
}

type Common struct {
	*Basic
	*SystemConfig
	common              CommonRepo
	redis               *redis.Redis
	wechatLoginSessions sync.Map
}

func NewCommon(
	basic *Basic,
	common CommonRepo,
	redis *redis.Redis,
) (*Common, error) {
	result := &Common{
		common: common,
		redis:  redis,
	}
	result.Basic = basic

	{
		// 初始化系统配置
		sc := newSystemConfig(result)
		// 系统配置 默认值初始化
		if err := sc.defaults.Load(systemCfgKeyValDefault); err != nil {
			return nil, err
		}
		// 查询系统已有配置
		ctx := context.Background()
		cfg, err := common.LoadSystemConfig(ctx, &v1.LoadSystemConfigRequest{})
		if err != nil {
			return nil, err
		}
		loads := make(map[string]string)
		for _, v := range cfg.Lists {
			if v.CfgKey != "" {
				loads[v.CfgKey] = v.GetCfgVal()
			}
		}
		// 加载系统已有配置
		if err = sc.Load(loads); err != nil {
			return nil, err
		}
		// 加载系统配置
		if sc.Len() == 0 {
			// 未加载配置; 使用默认配置初始化, 再持久化到存储媒介
			if err = sc.Load(systemCfgKeyValDefault); err != nil {
				return nil, err
			}
			if err = sc.Save(); err != nil {
				return nil, err
			}
		} else {
			// 已加载配置; 从持久化媒介中加载配置后, 校验配置是否符合预期
			if err = sc.VerifyLoad(); err != nil {
				return nil, err
			}
		}
		result.SystemConfig = sc
	}
	return result, nil
}

func (s *Common) LoadSystemConfig(ctx context.Context, req *v1.LoadSystemConfigRequest) (*v1.LoadSystemConfigReply, error) {
	return s.common.LoadSystemConfig(ctx, req)
}

func (s *Common) SaveSystemConfig(ctx context.Context, req *v1.SaveSystemConfigRequest) (*v1.SaveSystemConfigReply, error) {
	return s.common.SaveSystemConfig(ctx, req)
}

func (s *Common) SystemConfigLists(ctx context.Context, req *v1.SystemConfigListsRequest) (*v1.SystemConfigListsReply, error) {
	return s.common.SystemConfigLists(ctx, req)
}

func (s *Common) autoLoadAfterUpdate() error {
	tmp, err := s.LoadSystemConfig(context.Background(), &v1.LoadSystemConfigRequest{})
	if err != nil {
		return err
	}
	mp := make(map[string]string)
	for _, v := range tmp.Lists {
		mp[v.CfgKey] = v.GetCfgVal()
	}
	return s.SystemConfig.Load(mp)
}

func (s *Common) SystemConfigUpdate(ctx context.Context, req *v1.SystemConfigUpdateRequest) (*v1.SystemConfigUpdateReply, error) {
	reply, err := s.common.SystemConfigUpdate(ctx, req)
	if err != nil {
		return nil, err
	}
	go func() {
		for i := 0; i < 5; i++ {
			if err = s.autoLoadAfterUpdate(); err == nil {
				break
			}
			time.Sleep(time.Second)
		}
	}()
	return reply, nil
}

func (s *Common) TopicLists(ctx context.Context, req *v1.TopicListsRequest) (*v1.TopicListsReply, error) {
	return s.common.TopicLists(ctx, req)
}

var (
	wechatAppId     = "wx72a9fb13f6a2a2db"
	wechatAppSecret = "eea44b4516b47dd703a90c477ca6bced"
)

// func (s *Common) WechatLoginAuthUrl(ctx context.Context, req *v1.WechatLoginAuthUrlRequest) (*v1.WechatLoginAuthUrlReply, error) {
// 	reply := &v1.WechatLoginAuthUrlReply{}
// 	sessionId := util.RandomString(8, []byte(util.EnglishLetter+util.Number)...) + time.Now().Format("200060102150405") + util.RandomString(9, []byte(util.EnglishLetter+util.Number)...)
// 	key := fmt.Sprintf("wechat:login:%s", sessionId)
// 	value := fmt.Sprintf("%d", time.Now().Unix())
// 	err := s.redis.Client().Set(key, value, time.Minute*5).Err()
// 	if err != nil {
// 		return nil, err
// 	}
// 	authUrl := "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
// 	authUrl = fmt.Sprintf(
// 		authUrl,
// 		wechatAppId,
// 		url.QueryEscape("https://qw.leweif.com/v1/outer/login/wechat/callback"),
// 		"snsapi_login",
// 		sessionId,
// 	)
// 	reply = &v1.WechatLoginAuthUrlReply{
// 		StatusCode: 200,
// 		Message:    "success",
// 		Data: &v1.WechatLoginAuthUrlReply_Data{
// 			SessionId: sessionId,
// 			AuthUrl:   authUrl,
// 			QrcodeUrl: fmt.Sprintf("https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=%s", authUrl),
// 		},
// 	}
// 	return reply, nil
// }
//
// type AccessTokenResponse struct {
// 	AccessToken  string `json:"access_token"`
// 	ExpiresIn    int    `json:"expires_in"`
// 	RefreshToken string `json:"refresh_token"`
// 	OpenID       string `json:"openid"`
// 	Scope        string `json:"scope"`
// 	ErrCode      int    `json:"errcode"`
// 	ErrMsg       string `json:"errmsg"`
// }
//
// type WeChatUserInfo struct {
// 	OpenID     string `json:"openid"`
// 	Nickname   string `json:"nickname"`
// 	Sex        int    `json:"sex"`
// 	Province   string `json:"province"`
// 	City       string `json:"city"`
// 	Country    string `json:"country"`
// 	HeadImgURL string `json:"headimgurl"`
// 	UnionID    string `json:"unionid"`
// }
//
// func (s *Common) WechatLoginCallback(ctx context.Context, req *v1.WechatLoginCallbackRequest) (*v1.WechatLoginCallbackReply, error) {
// 	reply := &v1.WechatLoginCallbackReply{}
//
// 	tr, ok := transport.FromServerContext(ctx)
// 	if !ok {
// 		return nil, errors.New("not http")
// 	}
// 	trans, okt := tr.(*http.Transport)
// 	if !okt {
// 		return nil, errors.New("not http request")
// 	}
// 	request := trans.Request()
// 	query := request.URL.Query()
// 	code := query.Get("code")
// 	state := query.Get("state")
// 	if code == "" || state == "" {
// 		reply.Error = "parameter error"
// 		return reply, nil
// 	}
//
// 	redisClient := s.redis.Client()
// 	sessionId := state
// 	key := fmt.Sprintf("wechat:login:%s", sessionId)
// 	cmd := redisClient.Get(key)
// 	if err := cmd.Err(); err != nil {
// 		if errors.Is(err, libredis.Nil) {
// 			reply.Error = "state verify failed"
// 			return reply, nil
// 		}
// 		return nil, err
// 	}
//
// 	// 获取access_token
// 	tokenURL := fmt.Sprintf(
// 		"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
// 		wechatAppId,
// 		wechatAppSecret,
// 		code,
// 	)
// 	resp, err := stdhttp.Get(tokenURL)
// 	if err != nil {
// 		reply.Error = "get access_token failed"
// 		return reply, nil
// 	}
// 	defer resp.Body.Close()
//
// 	body, _ := io.ReadAll(resp.Body)
// 	var tokenResp AccessTokenResponse
// 	err = json.Unmarshal(body, &tokenResp)
// 	if err != nil {
// 		reply.Error = "json decode failed"
// 		return reply, nil
// 	}
// 	if tokenResp.ErrCode != 0 {
// 		return nil, errors.New(tokenResp.ErrMsg)
// 	}
// 	// 获取用户信息
// 	userInfoURL := fmt.Sprintf(
// 		"https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s",
// 		tokenResp.AccessToken,
// 		tokenResp.OpenID,
// 	)
//
// 	resp1, err := stdhttp.Get(userInfoURL)
// 	if err != nil {
// 		reply.Error = "get userinfo failed"
// 		return reply, nil
// 	}
// 	defer resp1.Body.Close()
//
// 	body, _ = io.ReadAll(resp1.Body)
// 	var userInfo WeChatUserInfo
// 	err = json.Unmarshal(body, &userInfo)
// 	if err != nil {
// 		reply.Error = "json decode failed"
// 		return reply, nil
// 	}
// 	dbUser, err := s.common.WechatLoginUserinfo(context.Background(), &v1.WechatLoginUserinfoRequest{
// 		Openid:     userInfo.OpenID,
// 		Nickname:   userInfo.Nickname,
// 		Province:   userInfo.Province,
// 		City:       userInfo.City,
// 		Country:    userInfo.Country,
// 		Sex:        int32(userInfo.Sex),
// 		Headimgurl: userInfo.HeadImgURL,
// 		Unionid:    userInfo.UnionID,
// 	})
// 	if err != nil {
// 		reply.Error = "get userinfo failed"
// 		return reply, nil
// 	}
// 	if dbUser.User == nil || dbUser.User.Id <= 0 {
// 		reply.Error = "auto register failed"
// 		return reply, nil
// 	}
// 	key = fmt.Sprintf("wechat:login:session:%s", sessionId)
// 	err = redisClient.Set(key, fmt.Sprintf("%d", dbUser.User.Id), time.Minute*5).Err()
// 	if err != nil {
// 		return nil, err
// 	}
// 	return reply, nil
// }
//
// func (s *Common) WechatLoginScan(ctx context.Context, req *v1.WechatLoginScanRequest) (*v1.LoginReply, error) {
// 	reply := &v1.LoginReply{}
// 	redisClient := s.redis.Client()
// 	key := fmt.Sprintf("wechat:login:session:%s", req.SessionId)
// 	cmd := redisClient.Get(key)
// 	if err := cmd.Err(); err != nil {
// 		if !errors.Is(err, libredis.Nil) {
// 			return nil, err
// 		}
// 		key1 := fmt.Sprintf("wechat:login:%s", req.SessionId)
// 		cmd1 := redisClient.Get(key1)
// 		if err = cmd1.Err(); err != nil {
// 			if !errors.Is(err, libredis.Nil) {
// 				return nil, err
// 			}
// 			reply.StatusCode = 200
// 			reply.Message = "登录超时"
// 			return reply, nil
// 		}
// 		if cmd1.Val() != "" {
// 			reply.StatusCode = 200
// 			reply.Message = "等待扫码"
// 			return reply, nil
// 		}
// 		reply.StatusCode = 200
// 		reply.Message = "登录超时"
// 		return reply, nil
// 	}
// 	uid, err := cmd.Int64()
// 	if err != nil {
// 		reply.StatusCode = 500
// 		reply.Message = "server error"
// 		return reply, nil
// 	}
// 	user, err := s.account.ServerGetUser(ctx, &v1.User{
// 		Id: uid,
// 	})
// 	if err != nil {
// 		reply.StatusCode = 500
// 		reply.Message = "server error"
// 		return reply, nil
// 	}
// 	token, err := s.BuildToken(ctx, user)
// 	if err != nil {
// 		reply.StatusCode = 500
// 		reply.Message = err.Error()
// 		return reply, nil
// 	}
// 	reply.Data = &v1.LoginReply_Reply{
// 		Token:       token.Token,
// 		PermitLists: token.UserPermit,
// 	}
// 	return reply, nil
// }

func (s *Common) getJson(uri string, receive any) error {
	response, err := stdhttp.Get(uri)
	if err != nil {
		return err
	}
	defer response.Body.Close()
	if response.StatusCode != stdhttp.StatusOK {
		return fmt.Errorf("error status: %d", response.StatusCode)
	}
	body := bytes.NewBuffer(nil)
	_, err = io.Copy(body, response.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body.Bytes(), receive)
	if err != nil {
		return err
	}
	return nil
}

func (s *Common) postJson(uri string, data any, receive any) error {
	bts, err := json.Marshal(data)
	if err != nil {
		return err
	}
	response, err := stdhttp.Post(uri, "application/json", bytes.NewBuffer(bts))
	if err != nil {
		return err
	}
	defer response.Body.Close()
	body := bytes.NewBuffer(nil)
	_, err = io.Copy(body, response.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body.Bytes(), receive)
	if err != nil {
		return err
	}
	return nil
}

type WechatResponseError struct {
	ErrCode int    `json:"errcode"` // 错误码
	ErrMsg  string `json:"errmsg"`  // 错误信息
}

func (s WechatResponseError) Err() error {
	if s.ErrCode != 0 {
		return fmt.Errorf("code: %d, mesage: %s", s.ErrCode, s.ErrMsg)
	}
	return nil
}

type AccessToken struct {
	WechatResponseError
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
}

func (s *Common) getAccessToken() (*AccessToken, error) {
	rc := s.redis.Client()
	key := "wechat:access_token"
	result := &AccessToken{}
	cmd := rc.Get(key)
	if err := cmd.Err(); err != nil {
		if !errors.Is(err, libredis.Nil) {
			return nil, err
		}
		uri := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
		uri = fmt.Sprintf(uri, wechatAppId, wechatAppSecret)
		err = s.getJson(uri, result)
		if err != nil {
			return nil, err
		}
		if err = result.Err(); err != nil {
			return nil, err
		}
		bts, _ := json.Marshal(result)
		cmd1 := rc.Set(key, string(bts), time.Second*7080)
		if cmd1.Err() != nil {
			return nil, cmd1.Err()
		}
		return result, nil
	}
	err := json.Unmarshal([]byte(cmd.Val()), result)
	if err != nil {
		return nil, err
	}
	if err = result.Err(); err != nil {
		return nil, err
	}
	return result, nil
}

type Ticket struct {
	WechatResponseError
	Ticket    string `json:"ticket"`     // 临时票据
	ExpiresIn int    `json:"expires_in"` // 有效期（秒）
}

func (s *Common) getTicket() (string, error) {
	accessToken, err := s.getAccessToken()
	if err != nil {
		return "", err
	}
	uri := "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"
	uri = fmt.Sprintf(uri, accessToken)
	ticket := &Ticket{}
	err = s.getJson(uri, ticket)
	if err != nil {
		return "", err
	}
	if err = ticket.Err(); err != nil {
		return "", err
	}
	return ticket.Ticket, nil
}

type QrCodeTicket struct {
	WechatResponseError
	Ticket        string `json:"ticket"`         // 获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码
	ExpireSeconds int    `json:"expire_seconds"` // 该二维码有效时间，以秒为单位。 最大不超过2592000（即30天）
	Url           string `json:"url"`            // 二维码图片解析后的地址，开发者可根据该地址自行生成需要的二维码图片
}

func (s *Common) getQrCodeTicket() (*QrCodeTicket, error) {
	accessToken, err := s.getAccessToken()
	if err != nil {
		return nil, err
	}
	body := make(map[string]any)
	body["expire_seconds"] = 86400
	body["action_name"] = "QR_STR_SCENE"
	actionInfo := make(map[string]any)
	actionInfoScene := make(map[string]any)
	sessionId := util.RandomString(8, []byte(util.EnglishLetter)...) + time.Now().Format("200060102150405") + util.RandomString(9, []byte(util.EnglishLetter+util.Number)...)
	actionInfoScene["scene_str"] = sessionId
	actionInfo["scene"] = actionInfoScene
	body["action_info"] = actionInfo
	uri := "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s"
	uri = fmt.Sprintf(uri, accessToken.AccessToken)
	resp := &QrCodeTicket{}
	err = s.postJson(uri, body, resp)
	if err != nil {
		return nil, err
	}
	if resp.Ticket == "" {
		return nil, errors.New(resp.ErrMsg)
	}
	s.redis.Client().HSet("wechat:login:ticket", sessionId, fmt.Sprintf("%d", time.Now().Unix()))
	return resp, nil
}

func (s *Common) WechatLoginAuthUrl(ctx context.Context, req *v1.WechatLoginAuthUrlRequest) (*v1.WechatLoginAuthUrlReply, error) {
	reply := &v1.WechatLoginAuthUrlReply{}
	reply.StatusCode = 200
	reply.Message = "success"
	ticket, err := s.getQrCodeTicket()
	if err != nil {
		return nil, err
	}
	if err = ticket.Err(); err != nil {
		reply.StatusCode = 500
		reply.Message = err.Error()
		return reply, nil
	}
	reply.Data = &v1.WechatLoginAuthUrlReply_Data{}
	reply.Data.Ticket = ticket.Ticket
	return reply, nil
}

type AccessTokenResponse struct {
	AccessToken  string `json:"access_token"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	OpenID       string `json:"openid"`
	Scope        string `json:"scope"`
	ErrCode      int    `json:"errcode"`
	ErrMsg       string `json:"errmsg"`
}

type WeChatUserInfo struct {
	OpenID     string `json:"openid"`
	Nickname   string `json:"nickname"`
	Sex        int    `json:"sex"`
	Province   string `json:"province"`
	City       string `json:"city"`
	Country    string `json:"country"`
	HeadImgURL string `json:"headimgurl"`
	UnionID    string `json:"unionid"`
}

var (
	wechatToken  = "MghDAypwhvJ9XvMUYnJPrwkBkygJe9wT"
	wechatAesKey = "dlW5Kk4F4v5033OexLnPh1LsjC3OklzASv2gsayk56B"
)

type WechatCallback struct {
	ToUserName string `json:"ToUserName"`
	Encrypt    string `json:"Encrypt"`
}

func (s *Common) WechatLoginCallback(ctx context.Context, req *v1.WechatLoginCallbackRequest) (*v1.WechatLoginCallbackReply, error) {
	reply := &v1.WechatLoginCallbackReply{}

	tr, ok := transport.FromServerContext(ctx)
	if !ok {
		return nil, errors.New("not http")
	}
	trans, okt := tr.(*http.Transport)
	if !okt {
		return nil, errors.New("not http request")
	}
	request := trans.Request()
	response := trans.Response()
	query := request.URL.Query()
	signature := query.Get("signature")
	timestamp := query.Get("timestamp")
	nonce := query.Get("nonce")
	if request.Method == stdhttp.MethodGet {
		echostr := query.Get("echostr")
		if signature == "" || timestamp == "" || nonce == "nonce" || echostr == "" {
			reply.Error = "parameter error"
			return reply, nil
		}
		strList := []string{wechatToken, timestamp, nonce}
		sort.Strings(strList)
		str := strings.Join(strList, "")
		h := sha1.New()
		h.Write([]byte(str))
		sha1value := hex.EncodeToString(h.Sum(nil))
		if signature != sha1value {
			return nil, errors.New("signature error")
		}
		response.Header().Set("Content-Type", "text/html; charset=utf-8")
		response.Header().Set("Content-Length", fmt.Sprintf("%d", len(echostr)))
		_, _ = response.Write([]byte(echostr))
		return &v1.WechatLoginCallbackReply{}, nil
	}

	msgSignature := query.Get("msg_signature")
	// todo 解析推送的数据包
	bodyBuffer := bytes.NewBuffer(nil)
	_, err := io.Copy(bodyBuffer, request.Body)
	if err != nil {
		return nil, err
	}
	body := &WechatCallback{}
	err = json.Unmarshal(bodyBuffer.Bytes(), body)
	if err != nil {
		return nil, err
	}

	strList := []string{wechatToken, timestamp, nonce, body.Encrypt}
	sort.Strings(strList)
	str := strings.Join(strList, "")
	h := sha1.New()
	h.Write([]byte(str))
	sha1value := hex.EncodeToString(h.Sum(nil))
	if msgSignature != sha1value {
		return nil, errors.New("signature error")
	}

	aesKey, err := base64.StdEncoding.DecodeString(wechatAesKey + "=")
	if err != nil {
		return nil, err
	}
	tmpMsg, err := base64.StdEncoding.DecodeString(body.Encrypt)
	if err != nil {
		return nil, err
	}
	// todo 解密数据
	_ = aesKey
	_ = tmpMsg

	return &v1.WechatLoginCallbackReply{}, nil
}

// Encrypt 加密
//
// plainText: 加密目标字符串
// key: 加密Key
// iv: 加密iv(AES时固定为16位)
func Encrypt(plainText string, key string, iv string) (string, error) {
	data, err := aesCBCEncrypt([]byte(plainText), []byte(key), []byte(iv))
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(data), nil
}

// Decrypt 解密
//
// cipherText: 解密目标字符串
// key: 加密Key
// iv: 加密iv(AES时固定为16位)
func Decrypt(cipherText string, key string, iv string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(cipherText)
	if err != nil {
		return "", err
	}

	dnData, err := aesCBCDecrypt(data, []byte(key), []byte(iv))
	if err != nil {
		return "", err
	}

	return string(dnData), nil
}

// aesCBCEncrypt AES/CBC/PKCS7Padding 加密
func aesCBCEncrypt(plaintext []byte, key []byte, iv []byte) ([]byte, error) {
	// AES
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	// PKCS7 填充
	plaintext = paddingPKCS7(plaintext, aes.BlockSize)

	// CBC 加密
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(plaintext, plaintext)

	return plaintext, nil
}

// aesCBCDecrypt AES/CBC/PKCS7Padding 解密
func aesCBCDecrypt(ciphertext []byte, key []byte, iv []byte) ([]byte, error) {
	// AES
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	if len(ciphertext)%aes.BlockSize != 0 {
		panic("ciphertext is not a multiple of the block size")
	}

	// CBC 解密
	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(ciphertext, ciphertext)

	// PKCS7 反填充
	result := unPaddingPKCS7(ciphertext)
	return result, nil
}

// PKCS7 填充
func paddingPKCS7(plaintext []byte, blockSize int) []byte {
	paddingSize := blockSize - len(plaintext)%blockSize
	paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)
	return append(plaintext, paddingText...)
}

// PKCS7 反填充
func unPaddingPKCS7(s []byte) []byte {
	length := len(s)
	if length == 0 {
		return s
	}
	unPadding := int(s[length-1])
	return s[:(length - unPadding)]
}

func (s *Common) WechatLoginScan(ctx context.Context, req *v1.WechatLoginScanRequest) (*v1.LoginReply, error) {
	reply := &v1.LoginReply{}
	redisClient := s.redis.Client()
	key := fmt.Sprintf("wechat:login:session:%s", req.SessionId)
	cmd := redisClient.Get(key)
	if err := cmd.Err(); err != nil {
		if !errors.Is(err, libredis.Nil) {
			return nil, err
		}
		key1 := fmt.Sprintf("wechat:login:%s", req.SessionId)
		cmd1 := redisClient.Get(key1)
		if err = cmd1.Err(); err != nil {
			if !errors.Is(err, libredis.Nil) {
				return nil, err
			}
			reply.StatusCode = 200
			reply.Message = "登录超时"
			return reply, nil
		}
		if cmd1.Val() != "" {
			reply.StatusCode = 200
			reply.Message = "等待扫码"
			return reply, nil
		}
		reply.StatusCode = 200
		reply.Message = "登录超时"
		return reply, nil
	}
	uid, err := cmd.Int64()
	if err != nil {
		reply.StatusCode = 500
		reply.Message = "server error"
		return reply, nil
	}
	user, err := s.account.ServerGetUser(ctx, &v1.FxUser{
		Id: uid,
	})
	if err != nil {
		reply.StatusCode = 500
		reply.Message = "server error"
		return reply, nil
	}
	token, err := s.BuildToken(ctx, user)
	if err != nil {
		reply.StatusCode = 500
		reply.Message = err.Error()
		return reply, nil
	}
	reply.Data = &v1.LoginReply_Reply{
		Token:       token.Token,
		PermitLists: token.UserPermit,
	}
	return reply, nil
}
